#include <yam/type.h>

#include <regex>

namespace yam {

struct FloatType : Type {
    FloatType() : Type("tag:yaml.org,2002:float", "scalar") {
        resolve = [](const yaml& data) {
            if (not data.is_string())
                return false;

            static const std::regex FLOAT_PATTERN(
                R"re(^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)?)re"
                R"re(|\.[0-9_]+(?:[eE][-+][0-9]+)?)re"
                R"re(|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*)re"
                R"re(|[-+]?\.(?:inf|Inf|INF))re"
                R"re(|\.(?:nan|NaN|NAN))$)re"
            );

            string str = data;
            return std::regex_match(str, FLOAT_PATTERN);
        };

        construct = [](const yaml& data) {
            string str = data;

            str.erase(std::remove_if(str.begin(), str.end(), [](char c) { return c == '_'; }), str.end());
            std::transform(str.begin(), str.end(), str.begin(), [](char c) { return std::tolower(c); });

            int sign = str[0] == '-' ? -1 : 1;

            if (str[0] == '+' or str[0] == '-')
                str = string(str.cbegin() + 1, str.cend());

            if (str == ".inf") {
                static_assert(std::numeric_limits<double>::is_iec559, "IEEE 754 required");
                return sign > 0 ? std::numeric_limits<double>::infinity() : -std::numeric_limits<double>::infinity();
            }
            else if (str == ".nan")
                return std::numeric_limits<double>::quiet_NaN();
            else if (str.find(':') != string::npos) {
                vector<double> digits;
                for (size_t start = 0, end = 0; end <= str.size(); ++end) {
                    if (end == str.size() or str[end] == ':') {
                        digits.push_back(std::stod(string(str.cbegin() + start, str.cbegin() + end)));
                        start = end + 1;
                    }
                }

                double value = 0.0;
                int base = 1;
                for (auto it = digits.crbegin(); it != digits.crend(); ++it) {
                    value += *it * base;
                    base *= 60;
                }

                return sign * value;
            }

            return sign * std::stod(str);
        };
    }
};

}
